// 2013年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 此文件实现方法集。

package types

import (
	"fmt"
	"sort"
	"strings"
)

// 方法集是一组有序的具体或抽象（接口）方法；
// 方法是MethodVal选择，它们按升序m.Obj（）.Id（）排序。
// MethodSet的零值是一个随时可用的空方法集。
type MethodSet struct {
	list []*Selection
}

func (s *MethodSet) String() string {
	if s.Len() == 0 {
		return "MethodSet {}"
	}

	var buf strings.Builder
	fmt.Fprintln(&buf, "MethodSet {")
	for _, f := range s.list {
		fmt.Fprintf(&buf, "\t%s\n", f)
	}
	fmt.Fprintln(&buf, "}")
	return buf.String()
}

// Len返回s中的方法数。
func (s *MethodSet) Len() int { return len(s.list) }

// At返回s中0<=i<s.Len（）的第i个方法。
func (s *MethodSet) At(i int) *Selection { return s.list[i] }

// Lookup返回具有匹配包和名称的方法，如果未找到，则返回nil。
func (s *MethodSet) Lookup(pkg *Package, name string) *Selection {
	if s.Len() == 0 {
		return nil
	}

	key := Id(pkg, name)
	i := sort.Search(len(s.list), func(i int) bool {
		m := s.list[i]
		return m.obj.Id() >= key
	})
	if i < len(s.list) {
		m := s.list[i]
		if m.obj.Id() == key {
			return m
		}
	}
	return nil
}

// 共享的空方法集。
var emptyMethodSet MethodSet

// 注：NewMethodSet仅供外部使用，因为它
// 要求接口完整。它可以被使用
// 如果LookupFieldOrMethod完成了相同的操作，则在内部执行
// 预先接口。

// NewMethodSet返回给定类型T的方法集。
// 它总是返回一个非nil方法集，即使它是空的。
func NewMethodSet(T Type) *MethodSet {
	// 警告：此函数中的代码非常微妙-不要随意修改！
	// 此函数和lookupFieldOrMethod应保持同步。

	// TODO（rfindley）确认此代码与lookupFieldOrMethod同步
	// 关于类型参数。

	// 方法设置为当前深度，延迟分配
	var base methodSet

	typ, isPtr := deref(T)

	// *typ，其中typ是一个接口，没有方法。
	if isPtr && IsInterface(typ) {
		return &emptyMethodSet
	}

	// 从最浅深度的单一入口开始。
	current := []embeddedType{{typ, nil, isPtr, false}}

	// 我们已经看到的命名类型是惰性分配的。
	// 用于避免递归类型情况下无休止的搜索。
	// 由于递归类型只能使用命名类型，因此
	// 只需要追踪那些。
	// （如果我们允许类型别名构造递归类型，
	// 我们必须使用类型标识，而不是指针相等
	// 地图键比较，正如我们在consolidateMultiples中所做的。）
	var seen map[*Named]bool

	// 在当前深度收集方法
	for len(current) > 0 {
		var next []embeddedType // 在当前深度找到嵌入类型

		// 当前深度的字段和方法集，按名称（Id）索引，并延迟分配
		var fset map[string]bool // 我们只关心字段名
		var mset methodSet

		for _, e := range current {
			typ := e.typ

			// 如果我们有一个命名类型，我们可能有关联的方法。
			// 先找那些。
			if named := asNamed(typ); named != nil {
				if seen[named] {
					// 我们以前在较浅的深度见过这种类型
					// （请注意，当前深度处该类型的倍数
					// 以前合并过）。该深度处的类型将显示阴影
					// 这是当前深度的同一类型，因此我们可以忽略
					// 这个。
					continue
				}
				if seen == nil {
					seen = make(map[*Named]bool)
				}
				seen[named] = true

				mset = mset.add(named.methods, e.index, e.indirect, e.multiples)

				// 继续使用基础类型，但前提是它不是类型参数
				// TODO（rFindley）：是否应该使用named.under（）？会有区别吗？
				typ = named.underlying
				if _, ok := typ.(*_TypeParam); ok {
					continue
				}
			}

			switch t := typ.(type) {
			case *Struct:
				for i, f := range t.fields {
					if fset == nil {
						fset = make(map[string]bool)
					}
					fset[f.Id()] = true

					// 嵌入字段的格式始终为T或*T，其中
					// T是一个类型名。如果typ在同一时间出现多次
					// 此深度，f.类型在下一次显示多次
					// 深度
					if f.embedded {
						typ, isPtr := deref(f.typ)
						// TODO（gri）优化：忽略无法
						// 具有字段或方法（仅命名、结构和
						// 需要考虑接口类型）。
						next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples})
					}
				}

			case *Interface:
				mset = mset.add(t.allMethods, e.index, true, e.multiples)

			case *_TypeParam:
				mset = mset.add(t.Bound().allMethods, e.index, true, e.multiples)
			}
		}

		// 如果没有匹配的条目，则在此深度向基添加方法和冲突
		// 名称已经存在。
		for k, m := range mset {
			if _, found := base[k]; !found {
				// 字段在此深度与同名的方法冲突。
				if fset[k] {
					m = nil // 碰撞
				}
				if base == nil {
					base = make(methodSet)
				}
				base[k] = m
			}
		}

		// 将此深度处的所有（剩余）字段添加为碰撞（因为它们将
		// 如果不存在具有匹配名称的条目，则隐藏任何向下的方法。
		for k := range fset {
			if _, found := base[k]; !found {
				if base == nil {
					base = make(methodSet)
				}
				base[k] = nil // 碰撞
			}
		}

		// 可以使用nil*检查器调用consolidateMultiples，因为
		// 方法集不在内部使用（在调试模式之外）。使用时
		// 从外部看，接口预计会完成，然后我们会完成
		// 在（间接）调用时，不需要*检查器来完成它们
		// 通过consolidateMultiples检查完全相同。
		current = (*Checker)(nil).consolidateMultiples(next)
	}

	if len(base) == 0 {
		return &emptyMethodSet
	}

	// 收集方法
	var list []*Selection
	for _, m := range base {
		if m != nil {
			m.recv = T
			list = append(list, m)
		}
	}
	// 按唯一名称排序
	sort.Slice(list, func(i, j int) bool {
		return list[i].obj.Id() < list[j].obj.Id()
	})
	return &MethodSet{list}
}

// methodSet是一组方法和名称冲突。
// 冲突表示具有
// 相同的唯一id，或出现具有该id的字段。
type methodSet map[string]*Selection // nil条目表示名称冲突

// Add将列表中的所有函数添加到方法集s。
// 如果设置了倍数，则列表中的每个函数都会出现多次
// 并被视为碰撞。
func (s methodSet) add(list []*Func, index []int, indirect bool, multiples bool) methodSet {
	if len(list) == 0 {
		return s
	}
	if s == nil {
		s = make(methodSet)
	}
	for i, f := range list {
		key := f.Id()
		// 如果f不在集合中，则添加它
		if !multiples {
			// TODO（gri）无法添加找到的方法，因为它不在方法集中
			// （！间接&ptrRecv（f））。同一级别的第二种方法可能在方法中
			// 设置并可能不会与第一个冲突，从而导致误报。
			// 可能吗？侦查
			if _, found := s[key]; !found && (indirect || !ptrRecv(f)) {
				s[key] = &Selection{MethodVal, nil, f, concat(index, i), indirect}
				continue
			}
		}
		s[key] = nil // 碰撞
	}
	return s
}

// ptrRecv报告接收方是否为*T格式。
func ptrRecv(f *Func) bool {
	// 如果设置了方法的接收器类型，则将其用作接收器的真值来源。
	// 注意：Checker.funcDecl（decl.go）通过将函数类型设置为空来标记函数
	// 签名我们可以在签名完全建立之前到达这里：我们必须明确
	// 检查接收机是否已设置（我们不能只查找非零f.typ）。
	if sig, _ := f.typ.(*Signature); sig != nil && sig.recv != nil {
		_, isPtr := deref(sig.recv.typ)
		return isPtr
	}

	// 如果未设置方法的类型，则可能是以下方法/函数：
	// 1） 提供的客户端（通过NewFunc提供，无签名），或
	// 2） 内部创建但尚未进行类型检查。
	// 对于案例1）我们什么都做不了；客户必须知道他们在做什么。
	// 对于案例2），我们可以使用解析器收集的信息。
	return f.hasPtrRecv
}
